#include<stdlib.h>
#include<stdio.h>
#include<strings.h>
#include<jansson.h>
#include "gauthenticate.h"
#include "gprinter.h"
#include "logger.h"
#include "utils.h"
#include <cups/file.h>
#include <cups/array.h>
#include <cups/cups.h>
#include <errno.h>
#include "gjob.h"

cups_array_t *Printers = NULL;
cups_array_t *CloudPrinters = NULL;
cups_array_t *CUPSPrinters = NULL;

const char *ServerRoot = "/etc";

static void clupsdLoadAllCloudPrinters(gservice* svc);

static int compare_printers(void *first, void *second, void *data) {
	clupsd_printer_t *lhs, *rhs;
 	lhs = (clupsd_printer_t*)first;
 	rhs = (clupsd_printer_t*)second;

	if(lhs == rhs) return 0;
	if(lhs->uuid && rhs->uuid)
		return strcmp(lhs->uuid, rhs->uuid);
	if(lhs->gid && rhs->gid)
		return strcmp(lhs->gid, rhs->gid);
	return strcmp(lhs->name, rhs->name);
}


int clupsdAddPrinter(cups_array_t **plist, clupsd_printer_t *p) {
	if(*plist == NULL) {
		*plist = cupsArrayNew(compare_printers, NULL);
	}

	if(*plist) {
		cupsArrayAdd(*plist, p);
		return 1;
	} else {
		return 0;
	}
}

clupsd_printer_t *clupsdNewPrinter(const char *name)
{
	clupsd_printer_t       *p;             /* New printer */

	if ((p = calloc(1, sizeof(clupsd_printer_t))) == NULL) {
	    	clupsdLogMessage(CLUPSD_ERROR, "Unable to allocate memory for printer-%s",
                    strerror(errno));
		return (NULL);
	}

	clupsdSetString(&p->name, name);
	clupsdSetString(&p->proxy, name);
	p->ppd = NULL;
	p->gid = NULL;
	p->uuid = NULL;
	p->state = NULL;
	
	return p;
}

void printer_destroy(clupsd_printer_t *ptr) {
	if (!ptr)
	  return;
	if(ptr->name) free(ptr->name); 
	if(ptr->proxy) free(ptr->proxy); 
	if(ptr->state) free(ptr->state); 
	if(ptr->ppd) free(ptr->ppd); 
	if(ptr->description) free(ptr->description); 
	if(ptr->uuid) free(ptr->uuid); 
	if(ptr->gid) free(ptr->gid); 
}


int get_cloud_printers(gservice *svc) {
   char *tokens[] = {"\"id\"", "\"name\"", "\"proxy\""};
   int result = get_url(svc, GCLOUDP_URL "/search", tokens, NULL, 0);
   if(result) {
				clupsdLogMessage(CLUPSD_ERROR,"Cannot get printers from Cloud Print Service");
   } else {
				clupsd_printer_t *printer;
				clupsdLoadAllCloudPrinters(svc);
  			for (printer = (clupsd_printer_t *)cupsArrayFirst(CloudPrinters);
       		printer;
       		printer = (clupsd_printer_t *)cupsArrayNext(CloudPrinters)) {
						printf("- %s\t%s\t%s\n", printer->name, printer->gid, printer->proxy);
				}
   }
}

clupsd_printer_t *find_cups_printer(const char *name) {
	int num_dests, i;
	cups_dest_t *dests;
	num_dests = cupsGetDests(&dests);
  if(num_dests <= 0) {cupsFreeDests(num_dests, dests); return NULL;}

  cups_dest_t *cups_printer = cupsGetDest(name, NULL, num_dests, dests);
  if(!cups_printer) {cupsFreeDests(num_dests, dests); return NULL;}

  clupsd_printer_t *p = clupsdNewPrinter(name);
  p->ppd = strdup(cupsGetPPD(name));
  
	const char* value;
	value = cupsGetOption("printer-info", cups_printer->num_options, cups_printer->options);
  if(!value) value = "no description available";
  p->description = strdup(value);

	value = cupsGetOption("printer-state", cups_printer->num_options, cups_printer->options);
  if(!value) value = "unknown";
  p->state = strdup(value);

	value = cupsGetOption("printer-location", cups_printer->num_options, cups_printer->options);
  if(!value) value = "unknown";
  p->location = strdup(value);

  cupsFreeDests(num_dests, dests);
	return p;
}


int get_cups_printers(gservice *svc) {
	int num_dests, i;
	cups_dest_t *dests;

	num_dests = cupsGetDests(&dests);
	printf("Found %d printers\n", num_dests);

  for(i=0; i<num_dests; i++) {
		if(dests[i].instance == NULL) {
			printf("%s\t%s\n", dests[i].name, cupsGetPPD(dests[i].name));
		}
  }

	cupsFreeDests(num_dests, dests);
}

int add_printer(gservice *svc, clupsd_printer_t *prn) {
   int result;

   struct curl_httppost *post=NULL;  struct curl_httppost *last=NULL;
   curl_formadd(&post, &last, CURLFORM_COPYNAME, "printer", CURLFORM_COPYCONTENTS, prn->name, CURLFORM_END);
   curl_formadd(&post, &last, CURLFORM_COPYNAME, "printerid", CURLFORM_COPYCONTENTS, prn->name, CURLFORM_END);
   curl_formadd(&post, &last, CURLFORM_COPYNAME, "proxy", CURLFORM_COPYCONTENTS, prn->proxy, CURLFORM_END);
   curl_formadd(&post, &last, CURLFORM_COPYNAME, "status", CURLFORM_COPYCONTENTS, prn->state, CURLFORM_END);
   curl_formadd(&post, &last, CURLFORM_COPYNAME, "description", CURLFORM_COPYCONTENTS, prn->description, CURLFORM_END);
   curl_formadd(&post, &last, CURLFORM_COPYNAME, "capabilities",   CURLFORM_FILECONTENT, prn->ppd, CURLFORM_END);
 
   result = get_url(svc, GCLOUDP_URL "/register", NULL, post, 0);
   if(result) {
     fprintf(stderr,"Oops: error: %d\n", result);
   } else {
	//printer is registered. Store this in our local database.
        //id, name and proxy
	//svc buffer contains json.
	json_error_t error;
	json_t *root = json_loads(svc->buffer, 0, &error);
 	if(!root) {	
		clupsdLogMessage(CLUPSD_ERROR, "line %d: %s\n", error.line, error.text);
	} else {
		json_t *printer_j = json_object_get(root, "printers");
		if (!json_is_array(printer_j)) {
			clupsdLogMessage(CLUPSD_ERROR, "No printers received from Google Cloud Print Service");
		} else if(json_array_size(printer_j) > 0) {
			json_t *this_printer_j = json_array_get(printer_j, 0);
			json_t *printer_id_j = json_object_get(this_printer_j, "id");
			if(!json_is_string(printer_id_j)) {
				clupsdLogMessage(CLUPSD_ERROR, "Printer id is not a string.");
			} else {
				PRINTER_SET(prn, gid, json_string_value(printer_id_j));
			}
		}
			
	}
	json_decref(root);
   }
   curl_formfree(post);
   return result;
}

int delete_printer(gservice *svc, char* printer_id) {
  int result;
  char url[1024];
  sprintf(url, "%s/delete?printerid=%s", GCLOUDP_URL,  printer_id);
  result = get_url(svc, url, NULL, NULL, 0);
  
  if(result) {
    clupsdLogMessage(CLUPSD_ERROR, "ERR %d - Cannot deregister printer from google Cloud services\n", result);
  } else {
    clupsdLogMessage(CLUPSD_DEBUG, "delete_printer() obj buffer: %s", svc->buffer);
  }
}

int printer_run_jobs(gservice *svc) {
   char *tokens[] = {"\"id\"", "\"name\"", "\"proxy\""};
   char url[1024];
   sprintf(url, "%s/search?proxy=%s", GCLOUDP_URL, svc->proxy);
   int result = get_url(svc, url, tokens, NULL, 0);
   if(result) {
	clupsdLogMessage(CLUPSD_ERROR,"Cannot get printer list from Cloud Print Service");
   } else {
	if (cupsArrayCount(CloudPrinters) > 0) { /** contains my registered printers. */
		/* process the print jobs for these printers. */
		process_all_jobs();
	} else {
		clupsdLogMessage(CLUPSD_NOTICE, "No printers registered for proxy=%s\n", svc->proxy);
	}
   }
}

/**
 * ProcessAllJobs
 *  ProcessJob
 **/
int process_job(gservice *svc, clupsd_printer_t *printer, clupsd_job_t *job) {
	/* get the file. */
	/* pass it to the cups printer */
	/* write status back */
}

int process_all_jobs(gservice *svc) {
  /* find all the active jobs for this and run them. */
  /* notified because the printer we registered has a print job. */
  int result;
  clupsd_printer_t *printer;
  for (printer = (clupsd_printer_t *)cupsArrayFirst(CloudPrinters);
       		printer;
       		printer = (clupsd_printer_t *)cupsArrayNext(CloudPrinters)) {
	  char url[1024];
	  	sprintf(url, "%s/fetch?printerid=%s", GCLOUDP_URL, printer->gid);
		result = get_url(svc, url, NULL, NULL, 0);
		if(result) {
			clupsdLogMessage(CLUPSD_ERROR,"Cannot get job list from Cloud Print Service for %s", printer->gid);
		} else {
			/* parse the json and process the jobs. */
			cups_array_t* jobs = clupsdParseJobs(svc, svc->buffer);
			if(jobs) {
				int i;
				for(i=0;i < cupsArrayCount(jobs);i++) {
					clupsd_job_t *job = (clupsd_job_t *) cupsArrayIndex(jobs, i);
					/* process the job */
					process_job(svc, printer, job);
				}
				clupsdFreeAllJobs(jobs);
			}
		}
  }
}

/* get all the printers from the CUPS service. */
void clupsdLoadAllCUPSPrinters(gservice *svc) 
{
	/* read all printers */
	/* sync all changed printers with our own cache. */
}

/* get all the printers from Cloud Print Service */
void clupsdLoadAllCloudPrinters(gservice *svc) 
{
	/* get all the printers */
	json_error_t error;
	json_t *root = json_loads(svc->buffer, 0, &error);
 	if(!root) {	
		clupsdLogMessage(CLUPSD_ERROR, "clupsdLoadAllCloudPrinters() error in json from Google Cloud Print on line %d: %s\n", error.line, error.text);
	} else {
		json_t *printer_j = json_object_get(root, "printers");
		if (!json_is_array(printer_j)) {
			clupsdLogMessage(CLUPSD_ERROR, "clupsdLoadAllCloudPrinters() json from Google Cloud Print does not include printer list");
		} else {
			int i;
			clupsd_printer_t *prn;
			for( i = 0; i < json_array_size(printer_j); i++) {
				const char 	*id = NULL, *name = NULL, *proxy = NULL;
				json_t *this_printer_j = json_array_get(printer_j, i);
				json_t *printer_id_j = json_object_get(this_printer_j, "id");
				if(!json_is_string(printer_id_j)) {
					clupsdLogMessage(CLUPSD_ERROR, "clupsdLoadAllCloudPrinters() printer id from Google Cloud Print is not a string");
				} else {
					id = json_string_value(printer_id_j);
				}
				json_t *printer_nm_j = json_object_get(this_printer_j, "name");
				if(!json_is_string(printer_nm_j)) {
					clupsdLogMessage(CLUPSD_ERROR, "clupsdLoadAllCloudPrinters() printer name from Google Cloud Print is not a string");
				} else {
					name = json_string_value(printer_nm_j);
				}
				json_t *printer_px_j = json_object_get(this_printer_j, "proxy");
				if(!json_is_string(printer_px_j)) {
					clupsdLogMessage(CLUPSD_ERROR, "clupsdLoadAllCloudPrinters() printer proxy from Google Cloud Print is not a string");
				} else {
					proxy = json_string_value(printer_px_j);
				}
					
				if(id && name && proxy) {
					prn = clupsdNewPrinter(name);
					PRINTER_SET(prn, gid, id);
					PRINTER_SET(prn, proxy, proxy);
					clupsdAddPrinter(&CloudPrinters, prn);
				}
			}
		}
			
	}
	json_decref(root);
}

#if 0
void clupsdLoadAllPrintersFromConfig(gservice *svc)
{
  int			i;		/* Looping var */
  cups_file_t		*fp;		/* printers.conf file */
  int			linenum;	/* Current line number */
  char			line[4096],	/* Line from file */
			*value,		/* Pointer to value */
			*valueptr;	/* Pointer into value */
  clupsd_printer_t	*p;		/* Current printer */


 /*
  * Open the printers.conf file...
  */
  snprintf(line, sizeof(line), "%s/clupsd.conf", ServerRoot);
  if ((fp = cupsFileOpen(line, "r")) == NULL)
    return;

 /*
  * Read printer configurations until we hit EOF...
  */
  linenum = 0;
  p       = NULL;

  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
  {
   /*
    * Decode the directive...
    */

    if (!_cups_strcasecmp(line, "<Printer") ||
        !_cups_strcasecmp(line, "<DefaultPrinter"))
    {
     /*
      * <Printer name> or <DefaultPrinter name>
      */
      if (p == NULL && value)
      {
       /*
        * Add the printer and a base file type...
	*/
        clupsdLogMessage(CLUPSD_NOTICE, "Loading printer %s...", value);

        p = clupsdNewPrinter(value);
				clupsdAddPrinter(&Printers, p);
      }
      else
        clupsdLogMessage(CLUPSD_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!_cups_strcasecmp(line, "</Printer>"))
    {
      if (p != NULL)
      {
        p = NULL;
      }
      else
        clupsdLogMessage(CLUPSD_ERROR,
	                "Syntax error on line %d of printers.conf.", linenum);
    }
    else if (!p)
    {
	if(!_cups_strcasecmp(line, "username")) {
		svc->username = strdup(value);
	} else if(!_cups_strcasecmp(line, "password")) {
		svc->password = strdup(value);
	} else if(!_cups_strcasecmp(line, "auth")) {
		svc->auth = strdup(value);
	} else {
      		clupsdLogMessage(CLUPSD_ERROR,
                      "Syntax error on line %d of printers.conf.", linenum);
	}
    }
    else if (!_cups_strcasecmp(line, "gid"))
    {
      if (value)
        clupsdSetString(&p->gid, value);
    }
    else if (!_cups_strcasecmp(line, "uuid"))
    {
      if (value)
        clupsdSetString(&p->uuid, value);
    }
    else if (!_cups_strcasecmp(line, "proxy"))
    {
      if (value)
	clupsdSetString(&p->proxy, value);
    }
    else if (!_cups_strcasecmp(line, "State"))
    {
      if (value)
	clupsdSetString(&p->state, value);
    }
    else 
    {
     /*
      * Something else we don't understand (and that wasn't used in a prior
      * release of CUPS...
      */

      clupsdLogMessage(CLUPSD_ERROR,
                      "Unknown configuration directive %s on line %d of "
		      "printers.conf.", line, linenum);
    }
  }

  cupsFileClose(fp);
}

void
clupsdSaveAllPrinters(gservice* svc)
{
  int			i;		/* Looping var */
  cups_file_t		*fp;		/* printers.conf file */
  char			filename[1024],	/* printers.conf filename */
			temp[1024],	/* Temporary string */
			value[2048],	/* Value string */
			*ptr,		/* Pointer into value */
			*name;		/* Current user/group name */
  clupsd_printer_t	*printer;	/* Current printer class */
  time_t		curtime;	/* Current time */
  struct tm		*curdate;	/* Current date */


 /*
  * Create the printers.conf file...
  */

  snprintf(filename, sizeof(filename), "%s/clupsd.conf", ServerRoot);

  if ((fp = cupsFileOpen(filename, "w")) == NULL)
    return;

  clupsdLogMessage(CLUPSD_NOTICE, "Saving printers.conf...");

 /*
  * Write a small header to the file...
  */

  curtime = time(NULL);
  curdate = localtime(&curtime);
  strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate);

  cupsFilePuts(fp, "# Printer configuration file for " CLUPSD_VERSION "\n");
  cupsFilePrintf(fp, "# Written by clupsd on %s\n", temp);
  cupsFilePuts(fp, "# DO NOT EDIT THIS FILE WHEN CLUPSD IS RUNNING\n");

  /* TBD: dont store plain text passwords */
  cupsFilePutConf(fp, "username", svc->username);
  cupsFilePutConf(fp, "password", svc->password);
  cupsFilePutConf(fp, "auth", svc->auth);

 /*
  * Write each local printer known to the system...
  */

  for (printer = (clupsd_printer_t *)cupsArrayFirst(Printers);
       printer;
       printer = (clupsd_printer_t *)cupsArrayNext(Printers))
  {
      cupsFilePrintf(fp, "<Printer %s>\n", printer->name);
      if (printer->uuid)
        cupsFilePutConf(fp, "uuid", printer->uuid);
      if (printer->gid)
        cupsFilePutConf(fp, "gid", printer->gid);
      if (printer->state)
        cupsFilePutConf(fp, "state", printer->state);
      if (printer->proxy)
        cupsFilePutConf(fp, "proxy", printer->state);
      cupsFilePuts(fp, "</Printer>\n");
  }

  cupsFileClose(fp);
}

#endif
